﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

namespace WpfCharting.Model.Style
{
    public class ChartStyleGridLines : ChartStyle
    {
        string title;
        string xLabel;
        string yLabel;

        Canvas textCanvas;

        bool isXGrid = true;
        bool isYGrid = true;

        double xTick = 1;
        double yTick = 0.5;
        double leftOffset = 20;
        double bottomOffset = 20;
        double rightOffset = 10;
        Line gridline = new Line();
        double _centerOfAxisCorrection = 0;

        private bool labelRotate = false;

        public ChartStyleGridLines()
        {
            this.XTickLabels = new Collection<string>();
            this.YTickLabels = new Collection<string>();
            this.GridLine = ChartGridLine.VerticalHorizontal;
        }

        public string Title
        {
            get { return title; }
            set { title = value; }
        }

        public string XLabel
        {
            get { return xLabel; }
            set { xLabel = value; }
        }

        public string YLabel
        {
            get { return yLabel; }
            set { yLabel = value; }
        }

        public Collection<string> XTickLabels
        {
            get;
            private set;
        }

        public Collection<string> YTickLabels
        {
            get;
            private set;
        }

        public double XTick
        {
            get { return xTick; }
            set { xTick = value; }
        }

        public double YTick
        {
            get { return yTick; }
            set { yTick = value; }
        }

        public Canvas TextCanvas
        {
            get { return textCanvas; }
            set { textCanvas = value; }
        }

        public bool IsXGrid
        {
            get { return isXGrid; }
            set { isXGrid = value; }
        }

        public bool IsYGrid
        {
            get { return isYGrid; }
            set { isYGrid = value; }
        }

        public void AddChartStyle(bool optimalXSpacing, bool optimalYSpacing)
        {
     //       if (tbTitle == null || tbXLabel == null || tbYLabel == null)
     //       {
     //           throw new ArgumentNullException(message: "Title and label must not be null", innerException: null);
     //       }

            if (this.XTickLabels != null && this.XTickLabels.Count > 0)
            {
                this.Xmin = 0;
                this.Xmax = XTickLabels.Count;
                this.xTick = 1;
                optimalXSpacing = false;
                //determine spacing space
                AxisAndGridLineSetUp(optimalXSpacing, optimalYSpacing);
            }
            else if (this.YTickLabels == null || this.YTickLabels.Count == 0)
            {
                AxisAndGridLineSetUp(optimalXSpacing, optimalYSpacing);
            }
            else if (this.YTickLabels != null || this.YTickLabels.Count > 0)
            {
                this.Ymin = 0;
                this.Ymax = this.YTickLabels.Count;
                this.YTick = 1;
                optimalYSpacing = false;
                //determine spacing space
                AxisAndGridLineSetUp(optimalXSpacing, optimalYSpacing);

            }
            else
            {
                AxisAndGridLineSetUp(optimalXSpacing, optimalYSpacing);
            }

            // Add title and labels:
            //            tbTitle.Text = Title;
            //            tbXLabel.Text = XLabel;
            //            tbYLabel.Text = YLabel;
          //  tbXLabel.Margin = new Thickness(leftOffset + 2, 2, 2, 2);
          //  tbTitle.Margin = new Thickness(leftOffset + 2, 2, 2, 2);
        }

        private void AxisAndGridLineSetUp(bool optimalx, bool optimaly)
        {
            Point pt = new Point();
            TextBlock tb = new TextBlock();
            double optimalXSpacing = 100;
            double optimalYSpacing = 80;

            //  determine right offset:
            Size size;
            RightOffset(tb, out size);

            // Determine left offset:
            double xScale = 0.0, yScale = 0.0;
            double xSpacing = 0.0, ySpacing = 0.0;
            double optimalXTick = 0.0, optimalYTick = 0.0;
            int xStart = 0, xEnd = 1;
            int yStart = 0, yEnd = 1;

            LeftOffset(tb, xScale, yScale, xSpacing, ySpacing, optimalXSpacing, optimalYSpacing, optimalXTick, optimalYTick, xStart, xEnd, yStart, yEnd, size, pt);

            DetermineXaxisLabelsFit(Math.Abs(TextCanvas.Width - leftOffset - rightOffset));

            Canvas.SetLeft(ChartCanvas, leftOffset);
            Canvas.SetBottom(ChartCanvas, bottomOffset);
            ChartCanvas.Width = Math.Abs(TextCanvas.Width - leftOffset - rightOffset);
            ChartCanvas.Height = Math.Abs(TextCanvas.Height - bottomOffset - size.Height / 2);
            Rectangle chartRect = new Rectangle();
            chartRect.Stroke = Brushes.Black;
            chartRect.Width = ChartCanvas.Width;
            chartRect.Height = ChartCanvas.Height;
            ChartCanvas.Children.Add(chartRect);

            if (Xmin != Xmax)
                xScale = (double)ChartCanvas.Width / (double)(Xmax - Xmin);
            if (Ymin != Ymax)
                yScale = (double)ChartCanvas.Height / (double)(Ymax - Ymin);
            xSpacing = optimalXSpacing / xScale;
            optimalXTick = OptimalSpacing(xSpacing);
            ySpacing = optimalYSpacing / yScale;
            optimalYTick = OptimalSpacing(ySpacing);
            xStart = (int)Math.Ceiling(Xmin / optimalXTick);
            xEnd = (int)Math.Floor(Xmax / optimalXTick);
            yStart = (int)Math.Ceiling(Ymin / optimalYTick);
            yEnd = (int)Math.Floor(Ymax / optimalYTick);

            VerticalGridLinesXTickMarks(optimalx, xStart, xEnd, optimalXTick, pt);
            HorizontalGridLinesYTickMarks(optimaly, yStart, yEnd, optimalYTick, pt);
        }

        void RightOffset(TextBlock tb, out Size size)
        {
            //  determine right offset:
            tb.Text = Math.Round(Xmax, 0).ToString(CultureInfo.CurrentCulture);
            tb.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
            size = tb.DesiredSize;
            rightOffset = size.Width / 2 + 2;
        }

        void LeftOffset(TextBlock tb, double xScale, double yScale, double xSpacing, double ySpacing, double optimalXSpacing, double optimalYSpacing, double optimalXTick, double optimalYTick
            , int xStart, int xEnd, int yStart, int yEnd, Size size, Point pt)
        {
            if (this.YTickLabels == null || this.YTickLabels.Count == 0)
            {
                double offset = 0;
                double dy; double offset0 = 30;

                while (Math.Abs(offset - offset0) > 1)
                {
                    if (Xmin != Xmax)
                        xScale = (double)(TextCanvas.Width - offset0 - rightOffset - 5) / (double)(Xmax - Xmin);
                    if (Ymin != Ymax)
                        yScale = (double)TextCanvas.Height / (double)(Ymax - Ymin);
                    xSpacing = optimalXSpacing / xScale;
                    optimalXTick = OptimalSpacing(xSpacing);
                    ySpacing = optimalYSpacing / yScale;
                    optimalYTick = OptimalSpacing(ySpacing);
                    xStart = (int)Math.Ceiling(Xmin / optimalXTick);
                    xEnd = (int)Math.Floor(Xmax / optimalXTick);
                    yStart = (int)Math.Ceiling(Ymin / optimalYTick);
                    yEnd = (int)Math.Floor(Ymax / optimalYTick);

                    for (int i = yStart; i <= yEnd; i++)
                    {
                        dy = i * optimalYTick;
                        pt = NormalizePoint(new Point(Xmin, dy));
                        tb = new TextBlock();
                        tb.Text = dy.ToString(CultureInfo.CurrentCulture);
                        tb.TextAlignment = TextAlignment.Right;
                        tb.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
                        size = tb.DesiredSize;
                        if (offset < size.Width)
                            offset = size.Width;
                    }
                    if (offset0 > offset)
                        offset0 -= 0.5;
                    else if (offset0 < offset)
                        offset0 += 0.5;
                }
                leftOffset = offset + 5;
            }
            else
            {
                foreach (var item in this.YTickLabels)
                {
                    TextBlock text = new TextBlock() { Text = item };
                    text.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
                    if (text.DesiredSize.Width + 5 > leftOffset)
                    {
                        leftOffset = text.DesiredSize.Width;
                    }
                }

            }


        }

        private void VerticalGridLinesXTickMarks(bool optimal, int xStart, int xEnd, double optimalXTick, Point pt)
        {
            double dx;
            // Create vertical gridlines and x tick marks:
            if (IsYGrid == true)
            {
                if (optimal)
                {
                    for (int i = xStart; i <= xEnd; i++)
                    {
                        gridline = new Line();
                        AddLinePattern();
                        dx = i * optimalXTick;
                        VerticalGridLines(dx);
                        CreateXTicks(dx, pt);
                    }
                }
                else
                {
                    for (dx = Xmin + XTick; dx <= Xmax; dx += XTick)
                    {
                        gridline = new Line();
                        AddLinePattern();
                        VerticalGridLines(dx);
                        CreateXTicks(dx, pt);
                    }

                }
            }
        }

        void VerticalGridLines(double dx)
        {
            if (this.GridLine == ChartGridLine.Vertical || this.GridLine == ChartGridLine.VerticalHorizontal)
            {
                gridline.X1 = NormalizePoint(new Point(dx, Ymin)).X;
                gridline.Y1 = NormalizePoint(new Point(dx, Ymin)).Y;
                gridline.X2 = NormalizePoint(new Point(dx, Ymax)).X;
                gridline.Y2 = NormalizePoint(new Point(dx, Ymax)).Y;
                ChartCanvas.Children.Add(gridline);
            }
        }

        private void CreateXTicks(double dx, Point pt)
        {
            pt = NormalizePoint(new Point(dx, Ymin));
            Line tick = new Line();
            tick.Stroke = Brushes.Black;
            tick.X1 = pt.X;
            tick.Y1 = pt.Y;
            tick.X2 = pt.X;
            tick.Y2 = pt.Y - 5;
            ChartCanvas.Children.Add(tick);

            TextBlock tb = new TextBlock();
            if (this.XTickLabels != null && this.XTickLabels.Count > 0)
            {
                if (dx == 1)
                {
                    _centerOfAxisCorrection = pt.X / 2;
                }
                if (dx != 0)
                {
                    tb.Text = this.XTickLabels[(int)dx - 1].ToString(CultureInfo.CurrentCulture);

                    tb.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
                    Size size = tb.DesiredSize;
                    TextCanvas.Children.Add(tb);


                    // Create a RotateTransform to rotate 
                    // the Polyline 90 degrees about the 
                    if (labelRotate)
                    {
                        Canvas.SetLeft(tb, leftOffset + pt.X - (_centerOfAxisCorrection - size.Height / 2));//- size.Width / 2
                        RotateTransform rotateTransform2 =
                             new RotateTransform(90);
                        tb.RenderTransform = rotateTransform2;

                    }
                    else
                    {
                        Canvas.SetLeft(tb, leftOffset + pt.X - (_centerOfAxisCorrection + size.Width / 2));//- size.Width / 2
                    }

                    Canvas.SetTop(tb, pt.Y + 2 + size.Height / 2);

                }

            }
            else
            {
                tb.Text = dx.ToString(CultureInfo.CurrentCulture);

                tb.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
                Size size = tb.DesiredSize;
                TextCanvas.Children.Add(tb);
                Canvas.SetLeft(tb, leftOffset + pt.X - size.Width / 2);
                Canvas.SetTop(tb, pt.Y + 2 + size.Height / 2);
            }

        }

        private void HorizontalGridLinesYTickMarks(bool optimal, int yStart, int yEnd, double optimalYTick, Point pt)
        {
            double dy;
            // Create horizontal gridlines and y tick marks:
            if (IsXGrid == true)
            {
                if (optimal)
                {
                    for (int i = yStart; i <= yEnd; i++)
                    {
                        gridline = new Line();
                        AddLinePattern();
                        dy = i * optimalYTick;
                        HorizontalGridLines(dy);
                        CreateYTicks(dy, pt);
                    }
                }
                else
                {
                    // Create y-axis tick marks:
                    for (dy = Ymin; dy <= Ymax; dy += YTick)
                    {
                        gridline = new Line();
                        AddLinePattern();
                        HorizontalGridLines(dy);
                        CreateYTicks(dy, pt);
                    }
                }
            }
        }

        void HorizontalGridLines(double dy)
        {
            if (this.GridLine == ChartGridLine.Horizontal || this.GridLine == ChartGridLine.VerticalHorizontal)
            {
                gridline.X1 = NormalizePoint(new Point(Xmin, dy)).X;
                gridline.Y1 = NormalizePoint(new Point(Xmin, dy)).Y;
                gridline.X2 = NormalizePoint(new Point(Xmax, dy)).X;
                gridline.Y2 = NormalizePoint(new Point(Xmax, dy)).Y;
                ChartCanvas.Children.Add(gridline);
            }
        }

        private void CreateYTicks(double dy, Point pt)
        {
            pt = NormalizePoint(new Point(Xmin, dy));
            Line tick = new Line();
            tick.Stroke = Brushes.Black;
            tick.X1 = pt.X;
            tick.Y1 = pt.Y;
            tick.X2 = pt.X + 5;
            tick.Y2 = pt.Y;
            ChartCanvas.Children.Add(tick);

            TextBlock tb = new TextBlock();
            if (this.YTickLabels != null && this.YTickLabels.Count > 0)
            {
                if (dy == 1)
                {
                    _centerOfAxisCorrection = pt.Y / 4;
                }
                if (dy != 0)
                {
                    tb.Text = this.YTickLabels[(int)dy - 1].ToString(CultureInfo.CurrentCulture);

                    tb.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
                    Size size = tb.DesiredSize;
                    TextCanvas.Children.Add(tb);
                    Canvas.SetRight(tb, ChartCanvas.Width + 10);
                    Canvas.SetTop(tb, pt.Y + _centerOfAxisCorrection);
                }
            }
            else
            {
                tb.Text = dy.ToString(CultureInfo.CurrentCulture);
                tb.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
                Size size = tb.DesiredSize;
                TextCanvas.Children.Add(tb);
                Canvas.SetRight(tb, ChartCanvas.Width + 10);
                Canvas.SetTop(tb, pt.Y);
            }
        }

        public void AddLinePattern()
        {
            gridline.Stroke = GridLineColor;
            gridline.StrokeThickness = 1;

            switch (GridLinePattern)
            {
                case ChartGridLinePattern.Dash:
                    gridline.StrokeDashArray = new DoubleCollection(new double[2] { 4, 3 });
                    break;
                case ChartGridLinePattern.Dot:
                    gridline.StrokeDashArray = new DoubleCollection(new double[2] { 1, 2 });
                    break;
                case ChartGridLinePattern.DashDot:
                    gridline.StrokeDashArray = new DoubleCollection(new double[4] { 4, 2, 1, 2 });
                    break;
            }
        }

        void DetermineXaxisLabelsFit(double chartCanvasWidth)
        {
            Size size = new Size();
            foreach (var item in this.XTickLabels)
            {
                TextBlock tb = new TextBlock() { Text = item };
                tb.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));

                if (size.Width < tb.DesiredSize.Width)
                {
                    size.Width = tb.DesiredSize.Width;
                }
                if (size.Height < tb.DesiredSize.Height)
                {
                    size.Height = tb.DesiredSize.Height;
                }
            }

            if (this.XTickLabels.Count > 0)
            {
                //check if there is enough room
                double d = chartCanvasWidth - leftOffset - rightOffset;
                var dd = d / this.XTickLabels.Count;
                if (dd > size.Width + 10)
                {
                    this.bottomOffset = size.Height + 5;
                    this.labelRotate = false;
                }
                else
                {
                    this.bottomOffset = size.Width + 5;
                    this.labelRotate = true;
                }
            }
        }

    }
}
